﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="XmlParser.cs" company="Elilink">
//   Defor
// </copyright>
// <summary>
//   Class for information exchange using XML format file
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace BusinessLayer.Parsers
{
    using System.Collections.Generic;
    using System.Linq;
    using System.Xml;
    using System.Xml.Linq;

    using BusinessLayer.Enums;
    using BusinessLayer.Objects;
    using BusinessLayer.Results;

    /// <summary>
    ///     Class for information exchange using XML format file
    /// </summary>
    public class XmlParser
    {
        #region Public Methods and Operators

        /// <summary>
        /// Creates the output file.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="OperationResult"/>.
        /// </returns>
        public static string CreateOutputFile(XmlDocumentModel model)
        {
            var document = new XDocument();
            var deploymentNode = new XElement(XmlElementConstant.DeploymentNode);
            deploymentNode.SetAttributeValue(XmlElementConstant.DeploymentSolutionPathAttribute, model.SolutionPath);
            deploymentNode.SetAttributeValue(XmlElementConstant.DeploymentLogsPathAttribute, model.LogsPath);
            foreach (TargetSystem targetSystem in model.TargetSystems)
            {
                var targetSystemElement = new XElement(XmlElementConstant.TargetNode);
                targetSystemElement.SetAttributeValue(
                    XmlElementConstant.TargetNameAttribute, 
                    targetSystem.TargetSystemName);
                targetSystemElement.SetAttributeValue(
                    XmlElementConstant.TargetBuildConfigurationAttribute, 
                    targetSystem.BuildConfiguration);
                foreach (DeployableProject deployableProject in targetSystem.DeployableProjects)
                {
                    var projectElement = new XElement(XmlElementConstant.ProjectNode);
                    projectElement.SetAttributeValue(
                        XmlElementConstant.ProjectNameAttribute, 
                        deployableProject.ProjectName);
                    projectElement.SetAttributeValue(
                        XmlElementConstant.ProjectPathAttribute, 
                        deployableProject.ProjectPath);
                    projectElement.SetAttributeValue(
                        XmlElementConstant.ProjectPublishProfileAttribute, 
                        deployableProject.PublishProfile);
                    projectElement.SetAttributeValue(
                        XmlElementConstant.ProjectUsernameAttribute, 
                        deployableProject.UserName);
                    projectElement.SetAttributeValue(
                        XmlElementConstant.ProjectPasswordAttribute, 
                        deployableProject.Password);
                    targetSystemElement.Add(projectElement);
                }

                deploymentNode.Add(targetSystemElement);
            }

            document.Add(deploymentNode);
            return document.ToString();
        }

        /// <summary>
        /// Parses the input file.
        /// </summary>
        /// <param name="file">
        /// The file.
        /// </param>
        /// <returns>
        /// The <see cref="OperationResult"/>.
        /// </returns>
        public static OperationResult ParseInputFile(XDocument file)
        {
            var result = new XmlDocumentModel { TargetSystems = new List<TargetSystem>() };
            List<XElement> baseAttributes = file.Descendants(XmlElementConstant.DeploymentNode).ToList();
            if (baseAttributes.Count != 1)
            {
                return new OperationResult(
                    ErrorType.ParseError,
                    string.Format("Can't find root '{0}' node.", XmlElementConstant.DeploymentNode));
            }

            XElement deployment = baseAttributes.First();
            result.SolutionPath = GetValue(deployment, XmlElementConstant.DeploymentSolutionPathAttribute);
            result.LogsPath = GetValue(deployment, XmlElementConstant.DeploymentLogsPathAttribute);
            List<XElement> targetElements = deployment.Descendants(XmlElementConstant.TargetNode).ToList();
            foreach (XElement targetElement in targetElements)
            {
                var targetSystem = new TargetSystem
                                       {
                                           BuildConfiguration =
                                               GetValue(
                                                   targetElement,
                                                   XmlElementConstant.TargetBuildConfigurationAttribute),
                                           TargetSystemName =
                                               GetValue(
                                                   targetElement,
                                                   XmlElementConstant.TargetNameAttribute),
                                           DeployableProjects = new List<DeployableProject>()
                                       };
                if (string.IsNullOrEmpty(targetSystem.TargetSystemName)
                    || string.IsNullOrEmpty(targetSystem.BuildConfiguration))
                {
                    var message =
                        string.Format(
                            "Required attributes '{1}' or '{2}' for a target are not specified at line {0}",
                            GetLineNumberForElement(targetElement),
                            XmlElementConstant.TargetBuildConfigurationAttribute,
                            XmlElementConstant.TargetNameAttribute);
                    return new OperationResult(ErrorType.ParseError, message);
                }

                bool targetAlreadyExists =
                    result.TargetSystems.FirstOrDefault(t => t.TargetSystemName == targetSystem.TargetSystemName)
                    != null;
                if (targetAlreadyExists)
                {
                    var message = string.Format(
                        "Duplicate target system with name {0} was found at line {1}",
                        targetSystem.TargetSystemName,
                        GetLineNumberForElement(targetElement));
                    return new OperationResult(ErrorType.ParseError, message);
                }

                List<XElement> deployableProjectElements =
                    targetElement.Descendants(XmlElementConstant.ProjectNode).ToList();
                foreach (XElement deployableProjectElement in deployableProjectElements)
                {
                    var deployableProject = new DeployableProject
                                                {
                                                    ProjectName =
                                                        GetValue(
                                                            deployableProjectElement,
                                                            XmlElementConstant.ProjectNameAttribute),
                                                    ProjectPath =
                                                        GetValue(
                                                            deployableProjectElement,
                                                            XmlElementConstant.ProjectPathAttribute),
                                                    PublishProfile =
                                                        GetValue(
                                                            deployableProjectElement,
                                                            XmlElementConstant.ProjectPublishProfileAttribute),
                                                    UserName =
                                                        GetValue(
                                                            deployableProjectElement,
                                                            XmlElementConstant.ProjectUsernameAttribute),
                                                    Password =
                                                        GetValue(
                                                            deployableProjectElement,
                                                            XmlElementConstant.ProjectPasswordAttribute),
                                                    TargetSystemName = targetSystem.TargetSystemName
                                                };
                    if (string.IsNullOrEmpty(deployableProject.ProjectName))
                    {
                        var message =
                            string.Format(
                                "Required attribute '{1}' for a project is not specified at line {0}",
                                GetLineNumberForElement(deployableProjectElement),
                                XmlElementConstant.ProjectNameAttribute);
                        return new OperationResult(
                            ErrorType.ParseError, message);
                    }

                    bool alreadyExist =
                        targetSystem.DeployableProjects.FirstOrDefault(
                            p => p.ProjectName == deployableProject.ProjectName) != null;
                    if (alreadyExist)
                    {
                        var message = string.Format(
                            "Duplicate project with name {0} was found at line {1}",
                            deployableProject.ProjectName,
                            GetLineNumberForElement(deployableProjectElement));
                        return new OperationResult(
                            ErrorType.ParseError, message);
                    }

                    targetSystem.DeployableProjects.Add(deployableProject);
                }

                result.TargetSystems.Add(targetSystem);
            }

            return new OperationResult(result);
        }

        #endregion

        #region Methods

        /// <summary>
        /// The get line number for element.
        /// </summary>
        /// <param name="element">
        /// The element.
        /// </param>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        private static int GetLineNumberForElement(XElement element)
        {
            return ((IXmlLineInfo)element).HasLineInfo() ? ((IXmlLineInfo)element).LineNumber : -1;
        }

        /// <summary>
        /// The get value.
        /// </summary>
        /// <param name="element">
        /// The element.
        /// </param>
        /// <param name="attributeName">
        /// The attribute name.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        private static string GetValue(XElement element, string attributeName)
        {
            XAttribute attribute = element.Attribute(attributeName);
            return attribute != null ? attribute.Value : string.Empty;
        }

        #endregion
    }
}